/**
 * 
 */
package pl.edu.mimuw.irs.core.model.design.net.refinement;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

import pl.edu.mimuw.irs.core.model.design.exception.InternalDesignException;
import pl.edu.mimuw.irs.core.model.design.exception.InternalDesignException.DesignCause;
import pl.edu.mimuw.irs.core.model.design.net.IRSNode;
import pl.edu.mimuw.irs.core.util.IPropertyContainer;

/**
 * Class representing direct children (by refinement pattern) of the {@link IRSNode}. This is tightly coupled with the
 * {@link Refinement} enumerator. Extensions of this class realizes the functionalities of the refinement patterns given
 * in that enumerator. Instances of this class knows how to build the refinement basing on the node we provided to the
 * constructor. This class is designed as non-modifiable. Once created cannot be changed. However, parent and all children 
 * can be modified. Restriction refers only to this instance, not the objects which are bound to it.
 * @author Jakub Rauch
 * Created on: 2010-02-25
 */
public abstract class IRSAbstractChildren implements IPropertyContainer, Iterable<IRSNode>, Cloneable, Serializable {

	private static final long serialVersionUID = 4206621994875728943L;
	
	// *** Attributes
	
	/** Listeners attached to this object. */
	private PropertyChangeSupport listeners;
	/** Direct parent containing this instance. */
	private IRSNode parent;
	/** All contained nodes (children). These are the nodes only from this level,
	 * not all descendants. */
	private List<IRSNode> allChildren;
	/** Contained nodes (children) that are defined as "input" children. These are the nodes (only from this level,
	 * not all descendants) that derive the incoming arcs from the parent. */
	private List<IRSNode> inputChildren;
	/** Contained nodes (children) that are defined as "output" children. These are the nodes (only from this level,
	 * not all descendants) that derive the outgoing arcs from the parent. */
	private List<IRSNode> outputChildren;

	/** The refinement corresponding to these object structure. */
	public abstract Refinement getRefinement();
	public PropertyChangeSupport getListeners() {
		if (listeners == null) listeners = new PropertyChangeSupport(this);
		return listeners;
	}
	public IRSNode getParent() {
		return this.parent;
	}
	/** @return the allChildren */
	public List<IRSNode> getAllChildren() {
		if (this.allChildren == null)
			this.allChildren = Collections.emptyList();
		return this.allChildren;
	}
	/** Can be set only once. Next time will throw an exception. This method also assigns the parent node
	 * of all the children.
	 * @param allChildren the allChildren to set */
	protected void setAllChildren(List<IRSNode> allChildren) {
		if (this.allChildren != null) throw new InternalDesignException(DesignCause.FINAL_FIELD_OVERRIDE, 
				"Tried to change allChildren of " + this, this.allChildren, allChildren);
		this.allChildren = Collections.unmodifiableList(allChildren);
		for (IRSNode child : allChildren) {
			child.setParent(this.parent);
		}
	}
	/** @return the inputChildren */
	public List<IRSNode> getInputChildren() {
		if (this.inputChildren == null)
			this.inputChildren = Collections.emptyList();
		return this.inputChildren;
	}
	/** Can be set only once. Next time will throw an exception.
	 * @param inputChildren the inputChildren to set */
	protected void setInputChildren(List<IRSNode> inputChildren) {
		// TODO : validation against allChildren
		if (this.inputChildren != null) throw new InternalDesignException(DesignCause.FINAL_FIELD_OVERRIDE, 
				"Tried to change inputChildren of " + this, this.inputChildren, inputChildren);
		this.inputChildren = Collections.unmodifiableList(inputChildren);
	}
	/** @return the outputChildren */
	public List<IRSNode> getOutputNodes() {
		if (this.outputChildren == null)
			this.outputChildren = Collections.emptyList();
		return this.outputChildren;
	}
	/** Can be set only once. Next time will throw an exception.
	 * @param outputChildren the outputChildren to set */
	protected void setOutputChildren(List<IRSNode> outputChildren) {
		// TODO : validation against allChildren
		if (this.outputChildren != null) throw new InternalDesignException(DesignCause.FINAL_FIELD_OVERRIDE, 
				"Tried to change outputChildren of " + this, this.outputChildren, outputChildren);
		this.outputChildren = Collections.unmodifiableList(outputChildren);
	}
	
	// *** Constructors

	/**
	 * Constructor of refinement for the given parent node. Depending on the class extension the refinement pattern will be
	 * performed accordingly using given parent node. Be sure to invoke final setter methods of this class:<br/>
	 * {@link #setAllChildren(List)}<br/>
	 * {@link #setInputChildren(List)}<br/>
	 * {@link #setOutputChildren(List)}<br/>
	 * These will be later used for the class operations.
	 * @param node
	 */
	public IRSAbstractChildren(IRSNode node) {
		this.parent = node;
	}
	
	/**
	 * Copying constructor. It does not perform {@link #allChildren}, {@link #inputChildren} and {@link #outputChildren}
	 * copy, because this can be done only once and possibly this abstract class would not know how to properly do it.
	 * Because of that, children must be manually attached to created object, and so this constructor must have its
	 * extension.
	 */
	public IRSAbstractChildren(IRSAbstractChildren children) {
		
	}
	
	// *** Methods

	@Override
	public void addPropertyChangeListener(PropertyChangeListener listener) {
		getListeners().addPropertyChangeListener(listener);
	}

	@Override
	public void addPropertyChangeListener(String property, PropertyChangeListener listener) {
		getListeners().addPropertyChangeListener(property, listener);
	}
	
	@Override
	public void removePropertyChangeListener(PropertyChangeListener listener) {
		getListeners().removePropertyChangeListener(listener);
	}
	
	@Override
	public void removePropertyChangeListener(String property, PropertyChangeListener listener) {
		getListeners().removePropertyChangeListener(property, listener);
	}
	
	/** Returns all sibling nodes that given node is connected to (even itself when self-arc exist). */
	public Set<IRSNode> getOutputSiblings(IRSNode node) {
		if (!getAllChildren().contains(node)) return Collections.emptySet();
		
		Set<IRSNode> outputSiblings = new LinkedHashSet<IRSNode>();
		for (IRSNode child : getAllChildren()) {
			/** If there is a self-connection we accept it as well */
			if (node.isConnectedTo(child)) outputSiblings.add(child);
		}
		
		return outputSiblings;
	}
	
	/** Returns all sibling nodes from which there are connections to the given node (even itself when self-arc exist). */
	public Set<IRSNode> getInputSiblings(IRSNode node) {
		if (!getAllChildren().contains(node)) return Collections.emptySet();
		
		Set<IRSNode> inputSiblings = new LinkedHashSet<IRSNode>();
		for (IRSNode child : getAllChildren()) {
			/** If there is a self-connection we accept it as well */
			if (child.isConnectedTo(node)) inputSiblings.add(child);
		}
		
		return inputSiblings;
	}
	
	@Override
	public Iterator<IRSNode> iterator() {
		return getAllChildren().iterator();
	}

	/** Method for cloning concrete nodes. By convention it should just invoke the copying constructor of this class. */
	public abstract IRSAbstractChildren clone();
}
